/*
* Copyright 2015-Present Entando S.r.l. (http://www.entando.com) All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package org.entando.entando.aps.system.services.cache;
import com.agiletec.aps.system.SystemConstants;
import com.agiletec.aps.system.common.AbstractService;
import com.agiletec.aps.system.exception.ApsSystemException;
import com.agiletec.aps.system.services.page.IPage;
import com.agiletec.aps.system.services.page.events.PageChangedEvent;
import com.agiletec.aps.system.services.page.events.PageChangedObserver;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.expression.EvaluationContext;
/**
* Manager of the System Cache
* @author E.Santoboni
*/
@Aspect
public class CacheInfoManager extends AbstractService implements ICacheInfoManager, PageChangedObserver {
private static final Logger _logger = LoggerFactory.getLogger(CacheInfoManager.class);
@Override
public void init() throws Exception {
_logger.debug("{} (cache info service initialized) ready", this.getClass().getName());
}
@Around("@annotation(cacheableInfo)")
public Object aroundCacheableMethod(ProceedingJoinPoint pjp, CacheableInfo cacheableInfo) throws Throwable {
Object result = pjp.proceed();
if (cacheableInfo.expiresInMinute() < 0 && (cacheableInfo.groups() == null || cacheableInfo.groups().trim().length() == 0)) {
return result;
}
try {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method targetMethod = methodSignature.getMethod();
Class targetClass = pjp.getTarget().getClass();
Method effectiveTargetMethod = targetClass.getMethod(targetMethod.getName(), targetMethod.getParameterTypes());
Cacheable cacheable = effectiveTargetMethod.getAnnotation(Cacheable.class);
if (null == cacheable) {
return result;
}
String[] cacheNames = cacheable.value();
Object key = this.evaluateExpression(cacheable.key().toString(), targetMethod, pjp.getArgs(), effectiveTargetMethod, targetClass);
for (int j = 0; j < cacheNames.length; j++) {
String cacheName = cacheNames[j];
if (cacheableInfo.groups() != null && cacheableInfo.groups().trim().length() > 0) {
Object groupsCsv = this.evaluateExpression(cacheableInfo.groups().toString(), targetMethod, pjp.getArgs(), effectiveTargetMethod, targetClass);
if (null != groupsCsv && groupsCsv.toString().trim().length() > 0) {
String[] groups = groupsCsv.toString().split(",");
this.putInGroup(cacheName, key.toString(), groups);
}
}
if (cacheableInfo.expiresInMinute() > 0) {
this.setExpirationTime(cacheName, key.toString(), cacheableInfo.expiresInMinute());
}
}
} catch (Throwable t) {
_logger.error("Error while evaluating cacheableInfo annotation", t);
throw new ApsSystemException("Error while evaluating cacheableInfo annotation", t);
}
return result;
}
@Around("@annotation(cacheInfoEvict)")
public Object aroundCacheInfoEvictMethod(ProceedingJoinPoint pjp, CacheInfoEvict cacheInfoEvict) throws Throwable {
try {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method targetMethod = methodSignature.getMethod();
Class targetClass = pjp.getTarget().getClass();
Method effectiveTargetMethod = targetClass.getMethod(targetMethod.getName(), targetMethod.getParameterTypes());
String[] cacheNames = cacheInfoEvict.value();
Object groupsCsv = this.evaluateExpression(cacheInfoEvict.groups().toString(), targetMethod, pjp.getArgs(), effectiveTargetMethod, targetClass);
if (null != groupsCsv && groupsCsv.toString().trim().length() > 0) {
String[] groups = groupsCsv.toString().split(",");
for (int i = 0; i < groups.length; i++) {
String group = groups[i];
for (int j = 0; j < cacheNames.length; j++) {
String cacheName = cacheNames[j];
this.flushGroup(cacheName, group);
}
}
}
} catch (Throwable t) {
_logger.error("Error while flushing group", t);
throw new ApsSystemException("Error while flushing group", t);
}
return pjp.proceed();
}
@Deprecated
public void setExpirationTime(String key, int expiresInMinute) {
this.setExpirationTime(DEFAULT_CACHE_NAME, key, expiresInMinute);
}
public void setExpirationTime(String targhetCache, String key, int expiresInMinute) {
Date expirationTime = DateUtils.addMinutes(new Date(), expiresInMinute);
this.setExpirationTime(targhetCache, key, expirationTime);
}
@Deprecated
public void setExpirationTime(String key, long expiresInSeconds) {
this.setExpirationTime(DEFAULT_CACHE_NAME, key, expiresInSeconds);
}
public void setExpirationTime(String targhetCache, String key, long expiresInSeconds) {
Date expirationTime = DateUtils.addSeconds(new Date(), (int) expiresInSeconds);
this.setExpirationTime(targhetCache, key, expirationTime);
}
public void setExpirationTime(String targhetCache, String key, Date expirationTime) {
Map<String, Date> expirationTimes = _objectExpirationTimes.get(targhetCache);
if (null == expirationTimes) {
expirationTimes = new HashMap<String, Date>();
_objectExpirationTimes.put(targhetCache, expirationTimes);
}
expirationTimes.put(key.toString(), expirationTime);
}
@Override
public void updateFromPageChanged(PageChangedEvent event) {
IPage page = event.getPage();
if (null != page) {
String pageCacheGroupName = SystemConstants.PAGES_CACHE_GROUP_PREFIX + page.getCode();
this.flushGroup(DEFAULT_CACHE_NAME, pageCacheGroupName);
}
}
@Override
protected void release() {
super.release();
this.destroy();
}
@Override
public void destroy() {
this.flushAll();
super.destroy();
}
public void flushAll() {
Collection<String> cacheNames = this.getSpringCacheManager().getCacheNames();
Iterator<String> iter = cacheNames.iterator();
while (iter.hasNext()) {
String cacheName = iter.next();
this.flushAll(cacheName);
}
}
public void flushAll(String cacheName) {
Cache cache = this.getCache(cacheName);
cache.clear();
Map<String, List<String>> objectsByGroup = this._cachesObjectGroups.get(cacheName);
if (null != objectsByGroup) {
objectsByGroup.clear();
}
}
@Override
@Deprecated
public void flushEntry(String key) {
this.flushEntry(null, key);
}
@Override
public void flushEntry(String targhetCache, String key) {
this.getCache(targhetCache).evict(key);
}
/**
* Put an object on the default cache.
* @param key The key
* @param obj The object to put into cache.
* @deprecated use putInCache(String, String, Object) instead
*/
public void putInCache(String key, Object obj) {
this.putInCache(DEFAULT_CACHE_NAME, key, obj);
}
/**
* Put an object on the given cache.
* @param targhetCache The cache name
* @param key The key
* @param obj The object to put into cache.
*/
public void putInCache(String targhetCache, String key, Object obj) {
Cache cache = this.getCache(targhetCache);
cache.put(key, obj);
}
@Deprecated
public void putInCache(String key, Object obj, String[] groups) {
this.putInCache(DEFAULT_CACHE_NAME, key, obj, groups);
}
public void putInCache(String targhetCache, String key, Object obj, String[] groups) {
Cache cache = this.getCache(targhetCache);
cache.put(key, obj);
this.accessOnGroupMapping(targhetCache, 1, groups, key);
}
@Deprecated
public Object getFromCache(String key) {
return getFromCache(DEFAULT_CACHE_NAME, key);
}
public Object getFromCache(String targhetCache, String key) {
Cache cache = this.getCache(targhetCache);
Cache.ValueWrapper element = cache.get(key);
if (null == element) {
return null;
}
if (isExpired(targhetCache, key)) {
this.flushEntry(targhetCache, key);
return null;
}
return element.get();
}
@Deprecated
public Object getFromCache(String key, int myRefreshPeriod) {
return this.getFromCache(key);
}
@Override
@Deprecated
public void flushGroup(String group) {
this.flushGroup(DEFAULT_CACHE_NAME, group);
}
@Override
public void flushGroup(String targhetCache, String group) {
String[] groups = {group};
this.accessOnGroupMapping(targhetCache, -1, groups, null);
}
@Override
@Deprecated
public void putInGroup(String key, String[] groups) {
this.accessOnGroupMapping(1, groups, key);
}
@Override
public void putInGroup(String targhetCache, String key, String[] groups) {
this.accessOnGroupMapping(DEFAULT_CACHE_NAME, 1, groups, key);
}
@Deprecated
protected synchronized void accessOnGroupMapping(int operationId, String[] groups, String key) {
this.accessOnGroupMapping(DEFAULT_CACHE_NAME, operationId, groups, key);
}
protected synchronized void accessOnGroupMapping(String targhetCache, int operationId, String[] groups, String key) {
Map<String, List<String>> objectsByGroup = this._cachesObjectGroups.get(targhetCache);
if (operationId>0) {
//add
if (null == objectsByGroup) {
objectsByGroup = new HashMap<String, List<String>>();
this._cachesObjectGroups.put(targhetCache, objectsByGroup);
}
for (int i = 0; i < groups.length; i++) {
String group = groups[i];
List<String> objectKeys = objectsByGroup.get(group);
if (null == objectKeys) {
objectKeys = new ArrayList<String>();
objectsByGroup.put(group, objectKeys);
}
if (!objectKeys.contains(key)) {
objectKeys.add(key);
}
}
} else {
//remove
if (null == objectsByGroup) {
return;
}
for (int i = 0; i < groups.length; i++) {
String group = groups[i];
List<String> objectKeys = objectsByGroup.get(group);
if (null != objectKeys) {
for (int j = 0; j < objectKeys.size(); j++) {
String extractedKey = objectKeys.get(j);
this.flushEntry(targhetCache, extractedKey);
}
objectsByGroup.remove(group);
}
}
}
}
protected Object evaluateExpression(String expression, Method method, Object[] args, Object target, Class<?> targetClass) {
Collection<Cache> caches = this.getCaches();
ExpressionEvaluator evaluator = new ExpressionEvaluator();
EvaluationContext context = evaluator.createEvaluationContext(caches,
method, args, target, targetClass, ExpressionEvaluator.NO_RESULT);
return evaluator.evaluateExpression(expression.toString(), method, context);
}
protected Collection<Cache> getCaches() {
Collection<Cache> caches = new ArrayList<Cache>();
Iterator<String> iter = this.getSpringCacheManager().getCacheNames().iterator();
while (iter.hasNext()) {
String cacheName = iter.next();
caches.add(this.getSpringCacheManager().getCache(cacheName));
}
return caches;
}
@Deprecated
public static boolean isNotExpired(String key) {
return isNotExpired(DEFAULT_CACHE_NAME, key);
}
public static boolean isNotExpired(String targhetCache, String key) {
return !isExpired(targhetCache, key);
}
@Deprecated
public static boolean isExpired(String key) {
return isExpired(DEFAULT_CACHE_NAME, key);
}
public static boolean isExpired(String targhetCache, String key) {
if (StringUtils.isBlank(targhetCache)) {
targhetCache = DEFAULT_CACHE_NAME;
}
Map<String, Date> expirationTimes = _objectExpirationTimes.get(targhetCache);
if (null == expirationTimes) {
return false;
}
Date expirationTime = expirationTimes.get(key);
if (null == expirationTime) {
return false;
}
if (expirationTime.before(new Date())) {
expirationTimes.remove(key);
return true;
} else {
return false;
}
}
protected Cache getCache(String cacheName) {
if (StringUtils.isBlank(cacheName)) {
cacheName = DEFAULT_CACHE_NAME;
}
return this.getSpringCacheManager().getCache(cacheName);
}
protected AbstractCacheManager getSpringCacheManager() {
return _springCacheManager;
}
public void setSpringCacheManager(AbstractCacheManager springCacheManager) {
this._springCacheManager = springCacheManager;
}
private AbstractCacheManager _springCacheManager;
private Map<String, Map<String, List<String>>> _cachesObjectGroups = new HashMap<String, Map<String, List<String>>>();
private static Map<String, Map<String, Date>> _objectExpirationTimes = new HashMap<String, Map<String, Date>>();
}